/*
 * Copyright 2025 arisgo@163.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.arisgo.cloud.web.param;

import com.arisgo.cloud.common.utils.ReflectUtil;
import com.arisgo.cloud.common.utils.StringUtil;
import com.arisgo.cloud.web.model.NumberIdWithTenantId;
import com.arisgo.cloud.web.model.NumberIdWithTime;
import com.arisgo.cloud.web.utils.RequestUtil;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.core.util.CollectionUtil;
import org.springframework.util.CollectionUtils;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * @author Coulson
 * @since 2023-8-22
 */
public class Search implements Serializable {

    private Selected selected;

    private List<Param> params;

    private List<Sort> sorts;

    public Search() {
    }

    public Search(Selected selected, List<Param> params, List<Sort> sorts) {
        this.selected = selected;
        this.params = params;
        this.sorts = sorts;
    }

    public static Search creator(Param... params) {
        return creator(null, Lists.newArrayList(params));
    }

    public static Search creator(Sort sort, Param... params) {
        return creator(null, Lists.newArrayList(params), Lists.newArrayList(sort));
    }

    public static Search creator(Selected selected, Param... params) {
        return creator(selected, Lists.newArrayList(params));
    }

    public static Search creator(Selected selected, List<Param> params) {
        return creator(selected, params, Lists.newArrayList(Sort.desc("id")));
    }

    public static Search creator(Selected selected, List<Param> params, List<Sort> sorts) {
        return new Search(selected, params, sorts);
    }

    public QueryWrapper buildWrapper() {
        QueryWrapper wrapper = new QueryWrapper();
        // 默认添加 tenantId
        Long tenantId = RequestUtil.getTenantId();
        if (!tenantId.equals(0L)) {
            addTenantIdParam(tenantId);
        }
        // 默认添加 isDeleted
        addIsDeletedParam();
        buildWrapper(wrapper);
        return wrapper;
    }

    public void buildWrapper(QueryWrapper wrapper) {
        if (null == wrapper) {
            return;
        }

        for (Param param : params) {
            param.buildWrapper(wrapper);
        }

        if (null != this.selected) {
            this.selected.buildWrapper(wrapper);
        }

        if (CollectionUtil.isNotEmpty(this.sorts)) {
            for (Sort sort : sorts) {
                sort.buildWrapper(wrapper);
            }
        }
    }

    public void addTenantIdParam(Long tenantId) {
        if (CollectionUtils.isEmpty(this.params)) {
            this.params = Lists.newArrayList();
        }
        String column = StringUtil.underlineToCamel(NumberIdWithTenantId.TENANT_ID);
        Optional<Param> optional = this.params.stream().filter(c -> column.equals(c.getField())).findFirst();
        if (optional.isEmpty()) {
            this.params.add(Param.creator(column, tenantId));
        } else {
            optional.get().setValue(tenantId);
        }
    }

    public void addIsDeletedParam() {
        if (CollectionUtils.isEmpty(this.params)) {
            this.params = Lists.newArrayList();
        }
        String column = StringUtil.underlineToCamel(NumberIdWithTime.IS_DELETED);
        Optional<Param> optional = this.params.stream().filter(c -> column.equals(c.getField())).findFirst();
        if (optional.isEmpty()) {
            this.params.add(Param.creator(column, false));
        }
    }

    public void addParam(Param param) {
        if (CollectionUtils.isEmpty((this.params))) {
            this.params = Lists.newArrayList();
        }
        this.params.add(param);
    }

    public void replaceParam(Param param) {
        if (CollectionUtils.isEmpty(this.params)) {
            return;
        }
        Optional<Param> optional = this.params.stream().filter(c -> c.getField().equals(param.getField())).findFirst();
        if (optional.isPresent()) {
            this.params.remove(optional.get());
            this.params.add(param);
        }
    }

    public Map<String, Object> convertMap() {
        Map<String, Object> data = Maps.newHashMap();
        addIsDeletedParam();
        for (Param param : params) {
            data.put(param.getField(), param.getValue());
        }
        return data;
    }

    public <T> T convert(Class<T> clazz) {
        try {
            T instance = clazz.getDeclaredConstructor().newInstance();
            if (CollectionUtils.isEmpty(params)) {
                return instance;
            }
            Map<String, Object> paramMap = this.params.stream().collect(Collectors.toMap(Param::getField, Param::getValue, (v1, v2) -> v1));
            List<Field> fields = ReflectUtil.getAllFields(clazz);
            for (Field field : fields) {
                String fieldName = field.getName();
                if (paramMap.containsKey(fieldName)) {
                    field.setAccessible(true);
                    field.set(instance, paramMap.get(fieldName));
                }
            }
            return instance;
        } catch (Exception e) {
            throw new RuntimeException("查询参数转换异常", e);
        }
    }

    public List<Param> getParams() {
        return params;
    }

    public void setParams(List<Param> params) {
        this.params = params;
    }

    public Selected getSelected() {
        return selected;
    }

    public void setSelected(Selected selected) {
        this.selected = selected;
    }

    public List<Sort> getSorts() {
        return sorts;
    }

    public void setSorts(List<Sort> sorts) {
        this.sorts = sorts;
    }

}
